package com.coderworm.tcp;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.net.Socket;
import java.nio.ByteBuffer;
import java.nio.charset.StandardCharsets;
import java.util.Random;
import java.util.zip.CRC32;

/**
 * 完整可运行的RtcmTcpClient类
 * 功能：模拟嵌入式设备，生成RTCM测试文件并发送给RtcmTcpServer
 * 特点：无外部依赖，包含所有必要方法，直接编译即可运行
 */
public class RtcmTcpClient {
    // 1. 配置服务端信息（需与RtcmTcpServer保持一致）
    private static final String SERVER_IP = "localhost"; // 服务端IP，本地测试用localhost
    private static final int SERVER_PORT = 8888; // 服务端端口，与服务端监听端口一致
    // 2. 配置测试文件信息
    private static final String TEST_RTCM_FILE = "D:\\test_rtcm_data.bin"; // 测试RTCM文件名
    private static final int TEST_FILE_SIZE = 1024 * 20; // 测试文件大小：20KB（可调整）

    public static void main(String[] args) {
        try {
            // 步骤1：生成符合RTCM格式的测试文件（含0xD3帧头）
            boolean fileCreated = createRtcmTestFile(TEST_RTCM_FILE, TEST_FILE_SIZE);
            if (!fileCreated) {
                System.err.println("测试文件生成失败，无法继续发送");
                return;
            }

            // 步骤2：连接服务端并发送测试文件
            sendRtcmFileToServer(TEST_RTCM_FILE);

        } catch (Exception e) {
            System.err.println("客户端运行异常：" + e.getMessage());
            e.printStackTrace();
        }
    }

    /**
     * 方法1：生成符合RTCM格式的测试文件
     * 关键特征：首字节为0xD3（RTCM标准帧头），其余为随机二进制数据（模拟真实观测值）
     * @param filePath 生成文件的路径
     * @param fileSize 生成文件的大小（单位：字节）
     * @return true：生成成功；false：生成失败
     */
    private static boolean createRtcmTestFile(String filePath, int fileSize) {
        // 校验参数：文件大小至少1字节（需包含0xD3帧头）
        if (fileSize < 1) {
            System.err.println("测试文件大小不能小于1字节");
            return false;
        }

        File testFile = new File(filePath);
        // 若文件已存在，先删除（避免使用旧文件）
        if (testFile.exists()) {
            boolean deleted = testFile.delete();
            if (!deleted) {
                System.err.println("旧测试文件删除失败，无法生成新文件");
                return false;
            }
        }

        // 写入RTCM格式数据
        try (OutputStream fos = new FileOutputStream(testFile);
             BufferedOutputStream bos = new BufferedOutputStream(fos)) {

            // 1. 写入RTCM标准帧头：0xD3（必须有，否则后续SDK无法识别）
            bos.write(0xD3);
            // 2. 生成剩余字节的随机二进制数据（模拟RTCM的观测值、导航电文等）
            byte[] randomData = new byte[fileSize - 1]; // 减1是因为已写入1字节帧头
            Random random = new Random();
            random.nextBytes(randomData); // 填充随机字节
            bos.write(randomData);

            // 强制刷新缓冲区，确保数据写入磁盘
            bos.flush();
            System.out.println("测试文件生成成功：");
            System.out.println("文件路径：" + testFile.getAbsolutePath());
            System.out.println("文件大小：" + testFile.length() + " 字节");
            return true;

        } catch (IOException e) {
            System.err.println("生成测试文件时出错：" + e.getMessage());
            e.printStackTrace();
            return false;
        }
    }

    /**
     * 方法2：连接服务端并发送RTCM文件
     * 严格遵循与RtcmTcpServer约定的协议：先传元数据→再传文件内容→最后校验
     * @param filePath 要发送的RTCM文件路径
     */
    private static void sendRtcmFileToServer(String filePath) throws IOException {
        File rtcmFile = new File(filePath);
        // 校验文件是否存在且为正常文件
        if (!rtcmFile.exists() || !rtcmFile.isFile()) {
            throw new IOException("RTCM文件不存在或不是有效文件：" + filePath);
        }

        // 1. 建立与服务端的TCP连接
        try (Socket clientSocket = new Socket(SERVER_IP, SERVER_PORT);
             // 2. 获取输入输出流（缓冲流提升效率）
             BufferedOutputStream serverOut = new BufferedOutputStream(clientSocket.getOutputStream());
             BufferedInputStream serverIn = new BufferedInputStream(clientSocket.getInputStream());
             BufferedReader serverReader = new BufferedReader(new InputStreamReader(serverIn, StandardCharsets.UTF_8));
             // 3. 读取本地RTCM文件的流
             BufferedInputStream fileIn = new BufferedInputStream(new FileInputStream(rtcmFile))) {

            System.out.println("已成功连接到服务端：" + SERVER_IP + ":" + SERVER_PORT);

            // -------------------------- 阶段1：发送文件元数据 --------------------------
            String fileName = rtcmFile.getName(); // 文件名（如"test_rtcm_data.bin"）
            long fileTotalSize = rtcmFile.length(); // 文件总大小（字节）

            // 1.1 发送文件名长度（2字节，无符号短整型，大端序）
            // 原因：服务端需先知道后续要读取多少字节作为文件名
            short fileNameLength = (short) fileName.length();
            byte[] fileNameLenBytes = ByteBuffer.allocate(2)
                    .order(java.nio.ByteOrder.BIG_ENDIAN) // 大端序，与服务端解析逻辑一致
                    .putShort(fileNameLength)
                    .array();
            serverOut.write(fileNameLenBytes);
            System.out.println("发送文件名长度：" + fileNameLength + " 字节（二进制：" + bytesToHex(fileNameLenBytes) + "）");

            // 1.2 发送文件名（UTF-8编码，长度=前2字节指定的fileNameLength）
            byte[] fileNameBytes = fileName.getBytes(StandardCharsets.UTF_8);
            serverOut.write(fileNameBytes);
            System.out.println("发送文件名：" + fileName + "（二进制：" + bytesToHex(fileNameBytes) + "）");

            // 1.3 发送文件总大小（8字节，无符号长整型，大端序）
            // 原因：服务端需知道要接收多少字节的文件内容，避免接收不完整
            byte[] fileSizeBytes = ByteBuffer.allocate(8)
                    .order(java.nio.ByteOrder.BIG_ENDIAN)
                    .putLong(fileTotalSize)
                    .array();
            serverOut.write(fileSizeBytes);
            System.out.println("发送文件总大小：" + fileTotalSize + " 字节（二进制：" + bytesToHex(fileSizeBytes) + "）");

            // 1.4 发送文件CRC32校验码（4字节，无符号整型，大端序）
            // 作用：服务端接收完文件后，可校验内容是否完整（避免传输损坏）
            long fileCrc32 = calculateFileCrc32(rtcmFile);
            byte[] crcBytes = ByteBuffer.allocate(4)
                    .order(java.nio.ByteOrder.BIG_ENDIAN)
                    .putInt((int) fileCrc32) // 强制转为int（CRC32值在int范围内）
                    .array();
            serverOut.write(crcBytes);
            System.out.println("发送文件CRC32校验码：" + fileCrc32 + "（二进制：" + bytesToHex(crcBytes) + "）");

            // 刷新元数据流，确保服务端能先接收到所有元数据
            serverOut.flush();
            System.out.println("元数据发送完成，开始发送文件内容...");

            // -------------------------- 阶段2：发送文件内容 --------------------------
            byte[] buffer = new byte[4096]; // 4KB缓冲区（平衡内存占用和IO效率）
            int bytesRead; // 每次从文件读取的字节数
            long totalSent = 0; // 已发送的总字节数（用于进度显示）

            // 循环读取文件并发送，直到文件读取完毕
            while ((bytesRead = fileIn.read(buffer)) != -1) {
                serverOut.write(buffer, 0, bytesRead); // 发送本次读取的字节
                totalSent += bytesRead;

                // 显示发送进度（每发送10%更新一次，避免频繁打印）
                int progress = (int) ((totalSent * 100.0) / fileTotalSize);
                if (progress % 10 == 0 && (totalSent == 0 || totalSent == fileTotalSize || progress != (int) (((totalSent - bytesRead) * 100.0) / fileTotalSize))) {
                    System.out.println("文件发送进度：" + progress + "%（已发送：" + totalSent + "/" + fileTotalSize + " 字节）");
                }
            }

            // 强制刷新缓冲区，确保所有文件内容发送到服务端
            serverOut.flush();
            System.out.println("文件内容发送完成，等待服务端响应...");

            // -------------------------- 阶段3：接收服务端响应 --------------------------
            // 服务端处理完成后会返回响应（如"SUCCESS"或"ERROR"），按行读取（服务端响应末尾加了\n）
            String serverResponse = serverReader.readLine();
            if (serverResponse == null) {
                throw new IOException("服务端连接异常关闭，未收到响应");
            }

            // 打印服务端响应，判断发送结果
            System.out.println("\n服务端响应：" + serverResponse);
            if (serverResponse.startsWith("SUCCESS")) {
                System.out.println("文件发送成功，任务完成！");
            } else {
                System.err.println("文件发送失败，服务端提示错误：" + serverResponse);
            }

        } catch (IOException e) {
            System.err.println("与服务端交互时出错：" + e.getMessage());
            throw e;
        }
    }

    /**
     * 辅助方法1：计算文件的CRC32校验码
     * 与RtcmTcpServer的CRC计算逻辑完全一致，确保校验能通过
     * @param file 要计算CRC的文件
     * @return CRC32校验码（无符号长整型）
     */
    private static long calculateFileCrc32(File file) throws IOException {
        CRC32 crc32 = new CRC32();
        try (BufferedInputStream bis = new BufferedInputStream(new FileInputStream(file))) {
            byte[] buffer = new byte[4096];
            int bytesRead;
            while ((bytesRead = bis.read(buffer)) != -1) {
                crc32.update(buffer, 0, bytesRead); // 逐段更新CRC值
            }
        }
        return crc32.getValue();
    }

    /**
     * 辅助方法2：字节数组转十六进制字符串（用于调试，查看发送的二进制数据）
     * @param bytes 要转换的字节数组
     * @return 十六进制字符串（如[0xD3, 0x00]→"D3 00"）
     */
    private static String bytesToHex(byte[] bytes) {
        if (bytes == null || bytes.length == 0) {
            return "";
        }
        StringBuilder sb = new StringBuilder();
        for (byte b : bytes) {
            sb.append(String.format("%02X ", b)); // 每个字节转2位十六进制，不足补0
        }
        return sb.toString().trim(); // 去除末尾空格
    }
}